package control;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import model.UserInfo;
import model.UserInfoClearText;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author Ragnarokr
 */
public final class Master {

// <editor-fold defaultstate="collapsed" desc="PROPERTIES">
    protected List<SlaveService> slaves;

    /**
     * Get the value of slaves
     *
     * @return the value of slaves
     */
    public List<SlaveService> getSlaves() {
        return slaves;
    }

    /**
     * Set the value of slaves
     *
     * @param slaves new value of slaves
     */
    public void setSlaves(List<SlaveService> slaves) {
        this.slaves = slaves;
    }
    protected List<String> dictionary;

    /**
     * Get the value of dictionary
     *
     * @return the value of dictionary
     */
    public List<String> getDictionary() {
        return dictionary;
    }

    /**
     * Set the value of dictionary
     *
     * @param dictionary new value of dictionary
     */
    public void setDictionary(List<String> dictionary) {
        this.dictionary = dictionary;
    }
    protected List<UserInfo> userInfo;

    /**
     * Get the value of userInfo
     *
     * @return the value of userInfo
     */
    public List<UserInfo> getUserInfo() {
        return userInfo;
    }

    /**
     * Set the value of userInfo
     *
     * @param userInfo new value of userInfo
     */
    public void setUserInfo(List<UserInfo> userInfo) {
        this.userInfo = userInfo;
    }
    protected int currentIndex = 0;

    /**
     * Get the value of currentIndex
     *
     * @return the value of currentIndex
     */
    public int getCurrentIndex() {
        return currentIndex;
    }

    /**
     * Set the value of currentIndex
     *
     * @param currentIndex new value of currentIndex
     */
    public void setCurrentIndex(int currentIndex) {
        this.currentIndex = currentIndex;
    }
    protected List<UserInfoClearText> results = new ArrayList<UserInfoClearText>();

    /**
     * Get the value of results
     *
     * @return the value of results
     */
    public List<UserInfoClearText> getResults() {
        return results;
    }

    /**
     * Set the value of results
     *
     * @param results new value of results
     */
    public void setResults(List<UserInfoClearText> results) {
        this.results = results;
    }
    protected List<List<String>> dParts;

    /**
     * Get the value of dParts
     *
     * @return the value of dParts
     */
    public List<List<String>> getDParts() {
        return dParts;
    }

    /**
     * Set the value of dParts
     *
     * @param dParts new value of dParts
     */
    public void setDParts(List<List<String>> dParts) {
        this.dParts = dParts;
    }
    // </editor-fold>
    public final static int SPLIT_SIZE = 2500;
    public final static int PORT = 42000;
    private long startTime = -1;

    /**
     * Creates a new instance of the class MasterController
     */
    public Master() {
        try {
            setSlaves(new ArrayList<SlaveService>());
            init();
        } catch (Exception ex) {
            Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Splits the dictionary into pieces with the SPLIT_SIZE chunk size
     */
    public void splitDictionary() {
        List<List<String>> splitted = new ArrayList<List<String>>();
        for (int i = 0; i < getDictionary().size(); i = i + SPLIT_SIZE) {
            int endIndex = i + SPLIT_SIZE - 1;
            if (endIndex > getDictionary().size()) {
                endIndex = getDictionary().size() - 1;
            }
            ArrayList<String> alPart = new ArrayList<String>(getDictionary().subList(i, endIndex));
            splitted.add(alPart);
        }
        setDParts(splitted);
    }

    /**
     * Tells if there are more users to be decrypted
     * @return TRUE if there are more users to be decrypted. Otherwise FALSE
     */
    public synchronized boolean moreUsersToBeFound() {
        return getUserInfo().size() != getResults().size();
    }

    /**
     * Initiates the slave connections
     */
    public void initSlaves() {
        ExecutorService es = Executors.newCachedThreadPool();
        try {
            ServerSocket ws = new ServerSocket(PORT);
            Socket s;
            while ((s = ws.accept()) != null) {
                if (startTime < 0) {
                    startTime = System.currentTimeMillis();
                }
                SlaveService ssh = new SlaveService(s, this);
                getSlaves().add(ssh);
                ssh.sendUserInfo(userInfo);
                es.submit(ssh);
            }
        } catch (IOException ex) {
            Logger.getLogger(Master.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Tells if there are more dictionary chunks to be sent to slaves
     * @return TRUE if there are more dictionary chunks to be sent to the slaves. Otherwise FALSE
     */
    public synchronized boolean moreInDictionary() {
        return (getCurrentIndex() < getDParts().size() - 1);
    }

    /**
     * Saves the partial results in a list, which will be printed then all
     * users have been found, or if there are no more dictionary chunks to be handled
     * @param partResults The results returned by a slave
     */
    public synchronized void handleResults(List<UserInfoClearText> partResults) {
        if (partResults != null && partResults.size() > 0) {
            getResults().addAll(partResults);
        }
        if (!moreUsersToBeFound()) {
            System.out.println(getResults());
            long endTime = System.currentTimeMillis();
            long timeSpend = (endTime - startTime) / 1000;
            System.out.println("time in seconds: " + timeSpend);
            System.out.println("minutes: " + (timeSpend / 60));
            System.out.println("seconds: " + (timeSpend % 60));
            System.exit(0);
        }
    }

    public synchronized List<String> getCurrentDPart() {
        List<String> dPart = getDParts().get(getCurrentIndex());
        incrementCurrentIndex();
        return dPart;
    }

    private synchronized void incrementCurrentIndex() {
        currentIndex++;
    }

    public void initDictionaryFile() throws Exception {
        File f = new File("webster-dictionary.txt");
        if (f.exists()) {
            BufferedReader br = new BufferedReader(new FileReader(f));
            String l;
            List<String> d = new ArrayList<String>();
            while ((l = br.readLine()) != null) {
                d.add(l);
            }
            setDictionary(d);
            splitDictionary();
        } else {
            throw new Exception("dictionary-file not found");
        }
    }

    public void initPasswordFile() throws Exception {
        FileReader fr = new FileReader("passwords.txt");
        BufferedReader br = new BufferedReader(fr);
        String l;
        List<UserInfo> ui = new ArrayList<UserInfo>();
        while ((l = br.readLine()) != null) {
            ui.add(convertToUserInfo(l));
        }
        setUserInfo(ui);
    }

    public static UserInfo convertToUserInfo(String ui) throws IOException {
        String[] s = ui.split(":");
        return new UserInfo(s[0], s[1]);
    }

    public void init() throws Exception {
        initDictionaryFile();
        initPasswordFile();
        initSlaves();
    }

    public static void main(String[] args) {
        Master mc = new Master();

    }
}
